﻿/***************************************************COPYRIGHT***************************************
* Copyright (c) 2008, Whiteboard-IT
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
* the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following 
 * disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the distribution.
*
* Neither the name of Whiteboard-IT, LLC nor the names of its contributors may be used to endorse or promote 
 * products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*****************************************************************************************************************/
using System;
using System.Collections.Generic;
using Boo.Lang.Compiler.Ast;

namespace Mite.DSL
{
	/// <summary>
	/// Transforms the contents of macro blocks specified in the ctor to parameters.
	/// <example>
	/// <code>
	/// mymacro:
	///		1
	///		"something"
	/// 
	/// is transformed into
	/// 
	/// mymacro(1, "something")
	/// </code>
	/// </example>
	/// </summary>
	public class BlockToArgumentsTransformer : DepthFirstTransformer
	{
		/// <summary>
		/// Creates an instance of BlockToArgumentsTransformer
		/// </summary>
		/// <param name="methods">Methods that should have blocks transformed into arguments.</param>
		public BlockToArgumentsTransformer(params string[] methods)
		{
			if (methods == null ||
			    methods.Length == 0)
				throw new ArgumentNullException("methods");
			this.methods = methods;
		}

		/// <summary>
		/// Handles macro statements.
		/// </summary>
		/// <param name="node">The node.</param>
		public override void OnMacroStatement(MacroStatement node)
		{
			if (Array.Exists(methods,
			                 delegate(string name) { return name.Equals(node.Name); }))
			{
				if (node.Block != null)
				{
					Expression[] expressions = GetExpressionsFromBlock(node.Block);
					foreach (Expression expression in expressions)
						node.Arguments.Add(expression);
					node.Block.Clear();
				}
			}
			base.OnMacroStatement(node);
		}

		private static Expression[] GetExpressionsFromBlock(Block block)
		{
			var expressions = new List<Expression>(block.Statements.Count);
			foreach (Statement statement in block.Statements)
			{
				if (statement is ExpressionStatement)
					expressions.Add((statement as ExpressionStatement).Expression);
				else if (statement is MacroStatement)
				{
					var macroStatement = statement as MacroStatement;
					if (macroStatement.Arguments.Count == 0 &&
					    !macroStatement.Block.HasStatements)
					{
						// Assume it is a reference expression
						var refExp = new ReferenceExpression(macroStatement.LexicalInfo);
						refExp.Name = macroStatement.Name;
						expressions.Add(refExp);
					}
					else
					{
						// Assume it is a MethodInvocation
						var mie = new MethodInvocationExpression(macroStatement.LexicalInfo);
						mie.Target = new ReferenceExpression(macroStatement.LexicalInfo, macroStatement.Name);
						mie.Arguments = macroStatement.Arguments;

						if (macroStatement.Block.HasStatements)
						{
							// If the macro statement has a block,                      
							// transform it into a block expression and pass that as the last argument                     
							// to the method invocation.
							var be = new BlockExpression(macroStatement.LexicalInfo);
							be.Body = macroStatement.Block.CloneNode();

							mie.Arguments.Add(be);
						}

						expressions.Add(mie);
					}
				}
				else
				{
					throw new InvalidOperationException(string.Format("Can not transform block with {0} into argument.",
					                                                  statement.GetType()));
				}
			}
			return expressions.ToArray();
		}

		private readonly string[] methods;
	}
}